// Icon view implementation for Mac OS X
// Copyright 2006 (C) Ralph Thomas

#include <view/icon.h>
#include <toolkit/mac/icon-mac.h>
#include "carbon_safe.hpp"
#include <binder.hpp>

namespace view {
	//
	/// The icon view is implemented using the HIImageView on Mac OS X.
	//
	class iconView {
		HIViewRef					_view;
		toolkit::icon*				_icon;
		adobe::binder_helper_t		_binder;
		toolkit::iconMac::iconSize	_size;
		//
		/// Update this iconView with a new icon.
		///
		/// \param	v	the value containing the icon.
		//
		void _update( const adobe::value_t& v ) {
			try {
				const toolkit::icon* icon = 0;
				const toolkit::iconMac* mac = 0;
				//
				// Try to extract the icon through an ugly mix of
				// value_t and RTTI.
				//
				v.get( icon );
				mac = dynamic_cast<const toolkit::iconMac*>( icon );
				if( mac ) {
					//
					// Set the image that we want.
					//
					HIImageViewSetImage( _view, mac->get( _size ) );
				} else HIImageViewSetImage( _view, 0 );
			} catch( ... ) {
				HIImageViewSetImage( _view, 0 );
			}
			HIViewSetNeedsDisplay( _view, true );
		}
	  public:
		iconView( adobe::sheet_t& sheet, const adobe::value_t& bind,
			toolkit::iconMac::iconSize size ) : _view( NULL ), _icon( 0 ),
			_binder( sheet ), _size( size ) {
			//
			// Create our icon widget. We actually use a transparent image
			// widget.
			//
			HIImageViewCreate( 0, &_view );
			HIImageViewSetOpaque( _view, false );
			HIViewSetVisible( _view, true );
			//
			// Glue our binder up to the thing we bind to. This has to be
			// the last thing that we do as it will call "_update" right away.
			//
			_binder.initialize( bind,
				boost::bind( &iconView::_update, this, _1 ), 0 );
		}
		~iconView() {
			DisposeControl( _view );
		}
		HIViewRef getView() const { return _view; }
		//
		// adobe::extents_t best_bounds()
		//
		/// Return the size of the icon which is going to be in here.
		///
		/// \return	the size of this icon.
		//
		adobe::extents_t best_bounds() {
			adobe::extents_t result;
			switch( _size ) {
			  case toolkit::iconMac::k16x16:
				result.width() = result.height() = 16;
				break;
			  case toolkit::iconMac::k22x22:
				result.width() = result.height() = 22;
				break;
			  case toolkit::iconMac::k32x32:
				result.width() = result.height() = 32;
				break;
			  case toolkit::iconMac::k48x48:
				result.width() = result.height() = 48;
				break;
			  case toolkit::iconMac::k64x64:
				result.width() = result.height() = 64;
				break;
			  case toolkit::iconMac::k128x128:
			  default:
				result.width() = result.height() = 128;
				break;
			}
			return result;
		}
		//
		// void place( const adobe::point_2d_t& position,
		//		const adobe::extents_t& geometry )
		//
		/// Place the widget in the given location under it's parent.
		///
		/// \param	position	the desired parent-relative
		///				position for this icon view.
		/// \param	geometry	the desired geometry for this
		///				icon view.
		//
		void place( const adobe::point_2d_t& position,
			const adobe::extents_t& geometry ) {
			assert( _view );
			CGRect r;
			r.origin.x = position.x_m;
			r.origin.y = position.y_m;
			r.size.width = geometry.width();
			r.size.height = geometry.height();
			HIViewSetFrame( _view, &r );
		}
	};

	adobe::widget_node_t icon_factory(
		const adobe::dictionary_t&		parameters,
		const adobe::widget_node_t&		parent,
		const adobe::factory_token_t&	token )
	{
		int size = 48;
		toolkit::iconMac::iconSize isize = toolkit::iconMac::k48x48;
		//
		// Extract the bind parameter and determine the size of this
		// icon view. We do these things first because they are most
		// likely to (correctly) cause exceptions.
		//
		adobe::value_t bind = parameters.at( adobe::static_name_t( "bind" ) );
		parameters.get<int>( adobe::static_name_t( "size" ), size );
		if( size == 16 )		isize = toolkit::iconMac::k16x16;
		else if( size == 22 )	isize = toolkit::iconMac::k22x22;
		else if( size == 32 )	isize = toolkit::iconMac::k32x32;
		else if( size == 48 )	isize = toolkit::iconMac::k48x48;
		else if( size == 64 )	isize = toolkit::iconMac::k64x64;
		else if( size == 128 )	isize = toolkit::iconMac::k128x128;
		else throw std::runtime_error( "icon: invalid size specified" );
		//
		// Create an iconView widget, and set it to delete when the
		// assemblage is destroyed.
		//
		iconView* iv( new iconView( token.sheet_m, bind, isize ) );
		token.assemblage_m.delete_on_destruction( iv );
		//
		// Insert this widget into it's parent on the main display.
		//
		ControlRef w( iv->getView() );
		adobe::display_t::position_t display_token( adobe::insert(
			adobe::get_main_display(), parent.display_token_m,
			w ) );
		adobe::display_t::position_t overlay_token(
			token.overlay_m.insert( parent.overlay_token_m ) );
		//
		// Bind the calculate and place signals for Eve.
		//
		adobe::eve_t::signal_suite_t signals;
		signals.eve_calculate_proc_m = boost::bind(
			&eve_client::utility_widget_calculate,
			iv->best_bounds(), _1 );
		eve_client::place_func_t placer(
			boost::bind( &iconView::place, iv, _1, _2 ) );
		signals.eve_place_proc_m = boost::bind(
			&eve_client::utility_widget_place,
			placer, _1, _2,
			boost::ref( token.overlay_m ), overlay_token );
		//
		// Register the widget with the layout engine.
		//
		adobe::eve_t::iterator eve_token(
			token.layout_m.add_view_element( parent.eve_token_m,
				adobe::eve_t::insert_element_t(
					adobe::eve_t::calculate_data_t(),
					false,
					parameters,
					signals )
				)
			);
		//
		// Return the icon view.
		//
		return adobe::widget_node_t( parent.size_m, eve_token,
			display_token, overlay_token );
	}
}